package com.example.thirdjarlearningstarter.autoconfigure;


import com.example.thirdjarlearningcore.ThirdJarBeanOne;
import com.example.thirdjarlearningcore.postProcessor.ThirdJarCotAsBeanScanRegister;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.stream.Stream;

/**
 * starter的核心
 */
// 需要用 @Configuration进行注解，因为在 这个类 中可能会创建新的Bean
@Configuration
// 声明当前配置类应该在 ThirdJarBeanOne 类加载完成后才加载，目的是保证创建 ThirdJarBeanOne Bean时，其元类信息已经加载完毕
@ConditionalOnClass({ThirdJarBeanOne.class})
// 声明当前配置类将会启用 ThirdJarLearningProperties 这个Properties类，此Properties将作为当前配置类的一个私有对象
@EnableConfigurationProperties({ThirdJarLearningProperties.class})
// 注意，虽然也是做Bean，但是不能用 @Bean，因为 @Bean 不能加在Type上
// 但为什么用 @Import 也不太明白。总之这里是在当前类中又写了一个会被SpringBoot自动加载的类
@Import({ThirdJarLearningAutoConfiguration.AutoConfiguredThirdJarCotAsBeanScanRegistrar.class})
public class ThirdJarLearningAutoConfiguration implements InitializingBean {

    private static final Logger LOGGER = LoggerFactory.getLogger(ThirdJarLearningAutoConfiguration.class);

    // 私有变量 ApplicationContext
    private final ApplicationContext applicationContext;
    // 私有变量 ThirdJarLearningProperties
    private final ThirdJarLearningProperties thirdJarLearningProperties;

    // 正常写构造函数即可，最普通的this赋值。但是这个构造函数中的值是怎么被传递进来的并不清楚
    public ThirdJarLearningAutoConfiguration(ThirdJarLearningProperties thirdJarLearningProperties, ApplicationContext applicationContext) {
        this.thirdJarLearningProperties = thirdJarLearningProperties;
        this.applicationContext = applicationContext;
    }

    /**
     * Invoked by the containing {@code BeanFactory} after it has set all bean properties
     * and satisfied {@link}, {@code ApplicationContextAware} etc.
     * <p>This method allows the bean instance to perform validation of its overall
     * configuration and final initialization when all bean properties have been set.
     *
     * @throws Exception in the event of misconfiguration (such as failure to set an
     *                   essential property) or if initialization fails for any other reason
     */
    @Override
    public void afterPropertiesSet() throws Exception {
    }

    /**
     * starter中创建 ThirdJarBeanOne 类的Bean，并注入到use的容器中，而非starter的容器中
     * 使用 @ConditionalOnMissingBean 注解是为了防止重复创建。该注解的含义是，当use的容器中不存在 ThirdJarBeanOne 类的Bean时，才会执行这个方法。
     * 使用 @Bean 注解当然就是因为这个方法的返回值将会被作为Bean对待
     * @return
     */
    @ConditionalOnMissingBean
    @Bean
    public ThirdJarBeanOne thirdJarBeanOne() {
        // 和使用@Bean创建Bean的过程完全相同
        // new 一个对应的类
        ThirdJarBeanOne thirdJarBeanOne = new ThirdJarBeanOne();
        // 修改类的属性值。这里使用 Properties类来赋值
        thirdJarBeanOne.setNickName(thirdJarLearningProperties.getNickName());
        thirdJarBeanOne.setAge(thirdJarLearningProperties.getAge());
        return thirdJarBeanOne;
    }


    /**
     * 一个实现了 BeanFactoryAware, ImportBeanDefinitionRegistrar 接口的类
     * 注意，若使用了 @Import(ImportClass.class)，则 ImportClass 必须实现 ImportBeanDefinitionRegistrar 接口
     */
    @ConditionalOnMissingBean({ThirdJarCotAsBeanScanRegister.class})
    public static class AutoConfiguredThirdJarCotAsBeanScanRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar{
        private BeanFactory beanFactory;

//        // 同样，这个类也是会被制作成Bean，所以构造函数，get/set函数写不写都行，毫不影响。
//        public AutoConfiguredThirdJarCotAsBeanScanRegistrar() {
//        }

        /**
         * BeanFactoryAware 接口必实现方法
         * 创建一个私有对象把传来的BeanFactory保存下来即可，之后会用到
         */
        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            this.beanFactory = beanFactory;
        }

        /**
         * ImportBeanDefinitionRegistrar 接口必实现方法
         * 还是向容器中存放一个BeanDefinition
         */
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
            if (!AutoConfigurationPackages.has(this.beanFactory)) {
                ThirdJarLearningAutoConfiguration.LOGGER.info("Could not determine auto-configuration package, automatic mapper scanning disabled.");
            } else {
                ThirdJarLearningAutoConfiguration.LOGGER.info("Searching for types annotated with @ThirdJarCotAsBean");
                List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
                if (ThirdJarLearningAutoConfiguration.LOGGER.isDebugEnabled()) {
                    packages.forEach((pkg) -> {
                        ThirdJarLearningAutoConfiguration.LOGGER.debug("Using auto-configuration base package '{}'", pkg);
                    });
                }
                // 重点是在这里，创建一个 BeanDefinitionBuilder，将 ThirdJarCotAsBeanScanRegister 构建成 BeanDefinition
                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ThirdJarCotAsBeanScanRegister.class);
                /**
                 * !!!!!重点来了，还记得在 ThirdJarCotAsBeanScanRegister 中有很多个私有对象嘛？但是那些私有对象既没有构造函数，也没有 get/set方法，那他们的初始值从哪来？
                 * 就是在这里，直接在制作成BeanDefinition的时候进行属性赋值
                 * 当然正是因为 ThirdJarCotAsBeanScanRegister 本身就是为了制作成Bean所以才没写构造函数和 get/set，普通的类当然别这样
                 */
                builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
                // 把这个BeanDefinition注册到容器中即可
                registry.registerBeanDefinition(ThirdJarCotAsBeanScanRegister.class.getName(), builder.getBeanDefinition());
            }
        }
    }

}
